################################################################################################
# 装饰器 decorator
# 场景一，添加标记
################################################################################################

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped


def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped


@makebold
@makeitalic
def hello():
    return "hello world"

# a = hello()
# print(a)


################################################################################################
# 装饰器 decorator
# 场景二，添加log
################################################################################################

import functools

def log(x):
    def decorator(func,text=x):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            func(*args, **kw)
            print('%s() End\n' % func.__name__)
            return
        return wrapper
    #这里判断x是否可被调用
    #针对@log('excute'): date()=log('excute')(date)，x='excute'不可被调用，则log('excute')返回的是函数decorator。
    #针对@log: now()=log(now)，x='now()'可以被调用，则log(now)返回的是decorator(now,'Call')
    return decorator(func=x,text='Call') if callable(x) else decorator

@log
def now():
    print('2017-07-28 Friday')

@log('excute')
def date():
    print('2017-07-28')

# now()
# date()

################################################################################################
# 装饰器 decorator
# 场景三，添加计时器
################################################################################################
import time

# 定义一个计时器，传入一个，并返回另一个附加了计时功能的方法
def timeit(func):
    # 定义一个内嵌的包装函数，给传入的函数加上计时功能的包装
    def wrapper(*args, **kwargs):
        start = time.clock()
        print(func(*args, **kwargs))
        end = time.clock()
        print('used:%f'%(end - start))
    # 将包装后的函数返回
    return wrapper

@timeit
def foo():
    print('in foo()')

# foo()

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

##############################################################################
# 一个关于记录被装饰函数运行时间的装饰器
##############################################################################

import time

def timeit(func):
    def wrapper(*args, **kwargs):
        start = time.clock()
        # 将函数返回值赋给变量，最后再return变量可以避免重复调用函数
        result = func(*args, **kwargs)
        end = time.clock()
        print('used:%f' % (end - start))
        return result
    return wrapper

@timeit
def eratosthenes1(max_num):
    i = 0
    prime_list = list(range(2, max_num + 1))
    while prime_list[i] ** 2 < prime_list[-1]:
        count = 0
        for j in range(i + 1, len(prime_list)):
            if prime_list[j] % prime_list[i] == 0:
                prime_list[j] = 0
                count += 1
        prime_list.sort()
        prime_list = prime_list[count:]
        i += 1
    return prime_list

##############################################################################
# 一个关于检测被装饰函数运行是否异常的装饰器
##############################################################################

def check_error(func):
    def wrapper(*args, **kwargs):
        try:
            func_result = func(*args, **kwargs)
            print('call "' + str(func.__name__) + '" pass!')
        except Exception as e:
            print('call "' + str(func.__name__) + '" fail: ' + str(e))
        return func_result
    return wrapper

@check_error
def int_(a, b):
    return int(a) + int(b)

##############################################################################
# 关于webdriver的装饰器，函数运行异常后自动调用webdriver进行截图
##############################################################################

def get_sceen(driver):
    nowTime = time.strftime("%Y-%m-%d_%H-%M-%S")
    driver.get_screenshot_as_file(r'C:\Downloads\%s.png' % nowTime)

def screen(func):
    def wrapper(driver, *args, **kwargs):
        func_result = None
        try:
            func_result = func(driver, *args, **kwargs)
            print('call "' + str(func.__name__) + '" pass!')
        except Exception as e:
            get_sceen(driver)
            print('call "' + str(func.__name__) + '" fail: ' + str(e))
        return func_result
    return wrapper

@screen
def search(driver):
    driver.get("https://www.baidu.com")
    driver.find_element_by_id("kw").send_keys("python")

##############################################################################
# 示例如何将一个类作为装饰器，该装饰器不带参数
##############################################################################

class DecoratorWithoutArguments(object):

    def __init__(self, func):
        print("Inside __init__()")
        self.func = func

    def __call__(self, *args):
        print("Inside __call__()")
        self.func(*args)
        print("After self.func(*args)")

@DecoratorWithoutArguments
def sayHello(a1, a2, a3, a4):
    print("sayHello arguments:", a1, a2, a3, a4)

print("After decoration")

print("Preparing to call sayHello()")
sayHello(1,2,3,4)
print("After first sayHello() call")
sayHello("w", "o", "o", "l")
print("After second sayHello() call")

"""
Inside __init__()
After decoration
Preparing to call sayHello()
Inside __call__()
sayHello arguments: 1 2 3 4
After self.func(*args)
After first sayHello() call
Inside __call__()
sayHello arguments: w o o l
After self.func(*args)
After second sayHello() call
"""

##############################################################################
# 示例如何将一个类作为装饰器，该装饰器带参数
##############################################################################

class DecoratorWithArguments(object):

    def __init__(self, arg1, arg2, arg3):
        print("Inside __init__()")
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def __call__(self, func):
        print("Inside __call__()")
        def wrapped_func(*args):
            print("Inside wrapped_func()")
            print("Decorator arguments: ", self.arg1, self.arg2, self.arg3)
            func(*args)
            print("After func(*args)")
        return wrapped_func

@DecoratorWithArguments("ins_ag1", "ins_ag2", "ins_ag3")
def sayHello(a1, a2, a3, a4):
    print("sayHello arguments:", a1, a2, a3, a4)

print("After decoration")

print("Preparing to call sayHello()")
sayHello(1,2,3,4)
print("After first sayHello() call")
sayHello("w", "o", "o", "l")
print("After second sayHello() call")

"""
Inside __init__()
Inside __call__()
After decoration
Preparing to call sayHello()
Inside wrapped_func()
Decorator arguments:  ins_ag1 ins_ag2 ins_ag3
sayHello arguments: 1 2 3 4
After func(*args)
After first sayHello() call
Inside wrapped_func()
Decorator arguments:  ins_ag1 ins_ag2 ins_ag3
sayHello arguments: w o o l
After func(*args)
After second sayHello() call
"""

##############################################################################
# 来个更复杂的装饰器，装饰函数自己带参数，并且还需要引用装饰器的参数
##############################################################################

def DecoratorFunctionWithArguments(arg1, arg2, arg3):
    def wrap(func):
        print("Inside wrap()")
        def wrapped_func(*args):
            print("Inside wrapped_func()")
            print("Decorator arguments: ", arg1, arg2, arg3)
            func(*args)
            print("After func(*args)")
        return wrapped_func
    return wrap

@DecoratorFunctionWithArguments("dec_ag1", "dec_ag2", "dec_ag3")
def sayHello(a1, a2, a3, a4):
    print("sayHello arguments:", a1, a2, a3, a4)
print("After decoration")

print("Preparing to call sayHello()")
sayHello(1,2,3,4)
print("After first sayHello() call")
sayHello("w", "o", "o", "l")
print("After second sayHello() call")

"""
Inside wrap()
After decoration
Preparing to call sayHello()
Inside wrapped_func()
Decorator arguments:  dec_ag1 dec_ag2 dec_ag3
sayHello arguments: 1 2 3 4
After func(*args)
After first sayHello() call
Inside wrapped_func()
Decorator arguments:  dec_ag1 dec_ag2 dec_ag3
sayHello arguments: w o o l
After func(*args)
After second sayHello() call
"""

# 将之前的例子改写，将get_screen作为子函数封装在screen之内，可写为：

from selenium import webdriver

def screen(driver):
    def wrapper(func):
        def wrapped_func(*args, **kwargs):
            func_result = None
            try:
                func_result = func(*args, **kwargs)
                print('call "' + str(func.__name__) + '" pass!')
            except Exception as e:
                now_time = time.strftime("%Y-%m-%d_%H-%M-%S")
                driver.get_screenshot_as_file(r'C:\Downloads\%s.png' % now_time)
                print('call "' + str(func.__name__) + '" fail: ' + str(e))
            finally:
                return func_result
        return wrapped_func
    return wrapper

browser = webdriver.Chrome(r"C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe")

@screen(browser)
def search(driver):
    driver.get("https://www.baidu.com")
    driver.find_element_by_id("kw22").send_keys("python")

search(browser)
browser.quit()


##############################################################################
# 关于webdriver的装饰器，和unittest结合，case失败后截图
##############################################################################

from selenium import webdriver
import unittest
import time
import inspect

class Screen(object):

    def __init__(self, driver):
        self.driver = driver

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            try:
                func_result = func(*args, **kwargs)
                print('    @%s passed!' % str(func.__name__), end='\n')
                return func_result
            except Exception as e:
                now_time = time.strftime("%Y-%m-%d_%H-%M-%S")
                self.driver.get_screenshot_as_file(r'C:\Downloads\%s.png' % str(func.__name__ + '_' + now_time))
                print('    @%s failed: %s' % (str(func.__name__), str(e)), end='\n')
                # 这里需要抛出异常，否则unittest检测不到异常，会认为case执行通过
                raise e
        return wrapper

class TestFixtures(unittest.TestCase):

    # 定义类属性
    browser = webdriver.Chrome(r"C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe")

    @property
    def __name__(self):
        return inspect.stack()[1][3]

    @classmethod
    def setUpClass(cls):
        print('******************** Start to test Class "%s" ********************' % str(cls.__name__), end='\n')
        cls.browser.implicitly_wait(30)
        cls.browser.maximize_window()
        cls.browser.get("https://www.baidu.com")

    @classmethod
    def tearDownClass(cls):
        print('\n')
        cls.browser.implicitly_wait(0)

    def setUp(self):
        self.browser.refresh()

    def tearDown(self):
        pass


class TestCasesP0(TestFixtures):

    # 父类的类属性可以被子类和子类的实例直接引用，但是在定义子类时却无法直接引用父类的类属性，所以需要指向一个新的属于子类的变量（属性）
    browser = TestFixtures.browser

    @Screen(browser)
    # case执行成功
    def test_search_01(self):
        print('<-------- Start to test Case "%s" -------->' % str(self.__name__))
        self.browser.find_element_by_id("kw").send_keys("Python3")
        self.browser.find_element_by_id("kw").submit()
        search_results = self.browser.find_elements_by_css_selector('.result.c-container em')
        for result in search_results:
            self.assertTrue('Python' == result.text or 'Python 3' == result.text or 'Python3' == result.text or
                            'python' == result.text or 'python 3' == result.text or 'python3' == result.text)

"""
******************** Start to test Class "TestCasesP0" ********************
<-------- Start to test Case "test_search_01" -------->
    @test_search_01 passed!
<-------- Start to test Case "test_search_02" -------->
    @test_search_02 failed: sequence item 0: expected str instance, int found


******************** Start to test Class "TestCasesP1" ********************
<-------- Start to test Case "test_search_01" -------->
    @test_search_01 failed: False is not true
<-------- Start to test Case "test_search_02" -------->
    @test_search_02 failed: Message: no such element: Unable to locate element: {"method":"id","selector":"kw123"}
    (Session info: chrome=64.0.3282.140)
    (Driver info: chromedriver=2.35.528161 (5b82f2d2aae0ca24b877009200ced9065a772e73),platform=Windows NT 10.0.16299 x86_64)
    
---------------------------------------------------------
Ran 4 tests in 59.085s
  
OK
"""


##############################################################################
# 装饰器在装饰函数的过程中难免会丢失一些信息，这时候就要用到 functools.wraps 了
##############################################################################

def outer(func):
    """ourter docstring"""
    def inner(*args, **kwargs):
        """inner docstring"""
        print("%s is called!" % func.__name__)
        return func(*args, **kwargs)
    return inner

@outer
def test_sum(a, b):
    """test_sum doc string"""
    return a + b

print(test_sum.__doc__)  # 打印的是装饰器的doc "inner docstring"



from functools import wraps

def outer(func):
    """ourter docstring"""
    @wraps(func)
    def inner(*args, **kwargs):
        """inner docstring"""
        print("%s is called!" % func.__name__)
        return func(*args, **kwargs)
    return inner

@outer
def test_sum(a, b):
    """test_sum doc string"""
    return a + b

print(test_sum.__doc__)  # 打印的是被装饰函数的doc "test_sum doc string"
